Welcome to YourTech, LLC. If you are unfamiliar with this site, you may want to find out about my services, read my story, or you could simply be looking for a way to contact me. On this site, you will find a collection of technical musings, howto guides, and technical reference information.

Wednesday, August 1, 2012

Exploring GateOne Browser SSH terminal

I came across a program called Gate One by LiftOff Software that just amazed me. This is an open-source, web-based ssh terminal. It is capable of multiple users, sessions, and bookmarks. I've tried a number of AJAX terminals or Java applet based ones in the past. The javascript ones usually did not have very good terminal emulation, while the Java apps worked, but worked just like a local desktop app (making it's own connection to port 22). Gate One uses WebSockets, allowing for full duplex communication through your web browser over the same port 80 or 443 used to serve up the web page.

Installation

Gate One is a python application using the Tornado framework. As such, at runs independently of an existing web server and handles connections from browsers internally. If you already have a web server running on your system, you will need to tell Gate One to use a different IP or a different port.

Installation using pre-built binaries or the source is fairly straightforward and detailed in the documentation.

The installer creates a directory of /opt/gateone and places all necessary files there. You can run it by changing to that directory and running gateone.py as root.

johnh@puppet2:/opt/gateone$ sudo ./gateone.py
[W 120801 13:52:06 terminal:166] Could not import the Python Imaging Library (PIL) so images will not be displayed in the terminal
[W 120801 13:52:06 gateone:2232] dtach command not found.  dtach support has been disabled.
[I 120801 13:52:06 gateone:1800] No authentication method configured. All users will be ANONYMOUS
[I 120801 13:52:06 gateone:1876] Loaded plugins: bookmarks, help, logging, logging_plugin, notice, playback, ssh
[I 120801 13:52:06 gateone:2329] Listening on https://*:443/

At this point, gateone is running in the foreground and you can view as connections occur and any errors. Pressing Ctrl If you conect to gateone using your webbrowser, you are logged in as user ANONYMOUS and can connect to any ssh host, either localhost or something remote.

If you edit /opt/gateone/server.conf, you can change authentication to "pam" or "google". Using pam will perform a Basic HTTP style authenication requiring a system-level username and password. Using google will log you in with your google account. Both of these "just work" without complicated setup.

Running as a Non-Root

Before I put something like this in production, I wanted to apply some additional security. First off, I want to see if I can get this to run as a non-root user.

Since gateone ran as root user initially, it has files owned by root
Only UID 0 can open ports below 1024.
gateone may need permission to write to system directories

To solve the first one, I chowned the /opt/gateone directory to my username. In the future, I'll want to run it under its own user, but I'll use mine for now for simplicity. To solve the second and third, I edited server.conf.

johnh@puppet2:/opt/gateone$ sudo chown -R johnh:johnh .
johnh@puppet2:/opt/gateone$ vi server.conf
# change/add the following lines appropriately
port = 2443
session_dir = "/opt/gateone/tmp/gateone"
pid_file = "/opt/gateone/tmp/gateone.pid"
uid = 1000
gid = 1000
johnh@puppet2:/opt/gateone$ ./gateone.py
[W 120801 14:06:01 terminal:166] Could not import the Python Imaging Library (PIL) so images will not be displayed in the terminal
[W 120801 14:06:01 gateone:2232] dtach command not found.  dtach support has been disabled.
[I 120801 14:06:01 gateone:1802] No authentication method configured. All users will be ANONYMOUS
[I 120801 14:06:01 gateone:1876] Loaded plugins: bookmarks, help, logging, logging_plugin, notice, playback, ssh
[I 120801 14:06:01 gateone:2329] Listening on https://*:2443/

Authentication

Running as a lower uid, you can use authentication of None or "google" without issue. If you use "pam", you discover you can only login with the username that gateone is running under. If you are the only intended user of the service, this may not be an issue. But if you want to allow other users, this becomes an issue. If you are fine with running as root or using Google as your authentication provider, you can ignore this next step.

Fortunately, pam is highly configurable. You aren't required to authenticate against shadow passwords. You can also authenticate against db4 files with pam_userdb, msyql, or even htpasswd files. To start off, I'm going to use htpasswd files. Note that Ubuntu doesn't provide pam_pwdfile.so by default. You need to install libpam-pwdfile ("sudo apt-get install libpam-pwdfile").

# Note - in testing, I discovered gateone uses Crypt encryption while htpasswd defaults to MD5. Use -d to switch to crypt encryption.
johnh@puppet2:/opt/gateone$ htpasswd -c -d users.passwd user1
New password:
Re-type new password:
Adding password for user user1
johnh@puppet2:/opt/gateone$ cat users.passwd
user1:KKEPyXfeqZZtU

Create a pam module called gateone under /etc/pam.d

johnh@puppet2:/opt/gateone$ cat /etc/pam.d/gateone
#%PAM-1.0
# Login using a htpasswd file
@include common-session
auth    required pam_pwdfile.so          pwdfile /opt/gateone/users.passwd
account required pam_permit.so

Modify server.conf to use pam and pam_service of gateone:

auth = "pam"
pam_service = "gateone"

Now start gateone and log in.

johnh@puppet2:~/g1/gateone$ ./gateone.py
[W 120801 14:59:16 terminal:168] Could not import the Python Imaging Library (PIL) so images will not be displayed in the terminal
[W 120801 14:59:16 gateone:2577] dtach command not found.  dtach support has been disabled.
[I 120801 14:59:16 gateone:2598] Connections to this server will be allowed from the following origins: 'http://localhost https://localhost http://127.0.0.1 https://127.0.0.1 https://puppet2 https://127.0.1.1 https://puppet2:2443'
[I 120801 14:59:16 gateone:2023] Using pam authentication
[I 120801 14:59:16 gateone:2101] Loaded plugins: bookmarks, help, logging, logging_plugin, mobile, notice, playback, ssh
[I 120801 14:59:16 gateone:2706] Listening on https://*:2443/
[I 120801 14:59:16 gateone:2710] Process running with pid 32591
[I 120801 14:59:17 gateone:949] WebSocket opened (user1@gateone).

One additional nice feature with authentication enabled is the ability to resume sessions - even across different computers or browsers.

Reverse Proxy

(I failed on this part, but felt it was worth recording)

Once I got it working in single user mode, I wanted to go ahead and set this up under a reverse proxy under Apache. This would allow me to integrate it into my existing web server under a sub-directory.

First, I edited server.conf to use a URL prefix of /g1/

Second, I tried setting up a ReverseProxy in Apache.

# GateOne Proxy
SSLProxyEngine On
ProxyPass /g1/ https://localhost:2443/g1/
ProxyPassReverse /g1/ https://localhost:2443/g1/
ProxyPassReverseCookieDomain localhost localhost
ProxyPassReverseCookiePath / /g1/

This almost worked. I had no errors, but the resulting page was unreadable. However, at the bottom was a clue. "The WebSocket connection was closed. Will attempt to reconnect every 5 seconds... NOTE: Some web proxies do not work properly with WebSockets." The problem was Apache not properly proxying my websocket connection. People have managed to get this working under nginx, but not Apache.

Searching for a solution led me to a similar question on ServerFault, an apache-websocket module on github, and a websocket tcp proxy based on that module.

  • http://serverfault.com/questions/290121/configuring-apache2-to-proxy-websocket
  • https://github.com/disconnect/apache-websocket
  • http://blog.alex.org.uk/2012/02/16/using-apache-websocket-to-proxy-tcp-connection/

In order to get this work, I'll need to download and compile some code. The apxs command requires the apache-prefork-dev package in Debian/Ubuntu. Install it with "sudo apt-get install apache-prefork-dev".

Now we are ready to download the code and install the module:

johnh@puppet2:~$ git clone https://github.com/disconnect/apache-websocket.git
Cloning into 'apache-websocket'...
.. done
johnh@puppet2:~$ wget http://blog.alex.org.uk/wp-uploads/mod_websocket_tcp_proxy.tar.gz
johnh@puppet2:~$ cd apache-websocket
johnh@puppet2:~/apache-websocket$ sudo apxs2 -i -a -c mod_websocket.c
*snip*
johnh@puppet2:~/apache-websocket$ sudo apxs2 -i -a -c mod_websocket_draft76.c
*snip*
johnh@puppet2:~$ cd examples
johnh@puppet2:~$ tar -xzvf ../../mod_websocket_tcp_proxy.tar.gz
mod_websocket_tcp_proxy.c
johnh@puppet2:~$ cd apache-websocket/examples/
johnh@puppet2:~/apache-websocket/examples$ sudo apxs2 -c -i -a -I.. mod_websocket_tcp_proxy.c
*snip*
chmod 644 /usr/lib/apache2/modules/mod_websocket_tcp_proxy.so
[preparing module `websocket_tcp_proxy' in /etc/apache2/mods-available/websocket_tcp_proxy.load]
Enabling module websocket_tcp_proxy.
To activate the new configuration, you need to run:
service apache2 restart
johnh@puppet2:~$

Before we restart, I want to remove my Proxy lines and replace them with the mod_websocket_tcp_proxy lines.


    
        SetHandler websocket-handler
        WebSocketHandler  /usr/lib/apache2/modules/mod_websocket_tcp_proxy.so tcp_proxy_init
        WebSocketTcpProxyBase64 on
        WebSocketTcpProxyHost 127.0.0.1
        WebSocketTcpProxyPort 2443
        WebSocketTcpProxyProtocol base64
    

Despite all this, I was still unable to get this to work. I even attempted using the web root (/) as my location. If the Location matches and your HTTP request is handled by mod_websocket, you get a 404. If you use Proxy, then your websocket request is handled by mod_proxy. Mod_proxy wins out over Location matches. Perhaps you can modify gateone code to have one URL for the web interface and one for websockets (or maybe it's already in place and we just need to know), but I don't see a way at this time to get this working under Apache. I may be able to work with the gateone author and the mod_websocket_tcp_proxy.c author to come up with a solution. Or I could try installing nginx. In the meantime, I can continue to run Open Gate as a non-root user on a non-standard port. Alternatively, I could find a wrapper to bring port 443 to 2443.

8 comments:

  1. Hi there... I'm the author of Gate One. Excellent article! I just wanted to let you know that if you configure the uid and gid options to something other than root then run Gate One as root it will drop privileges to the provided uid/gid. Not sure if this works around the issue of only being able to authenticate as that uid/gid though.

    Also, Gate One *should* work with that Apache WebSocketHandler configuration... I just think you missed a step :)

    Since you're proxying /g1/ you need to set url_prefix="/g1/" in your server.conf so Gate One will know where it lives from a URL perspective.

    Once you make that change it should start working. Please give it a try and let us all know how it goes!

    ReplyDelete
    Replies
    1. Hi Dan, thanks for the comments. I do have my uid/gid set to 1000, and that does drop privileges, but you need privileges to log in as a different system user. A good example of this is to run "login" in Linux as a non-privileged user. You go through the steps, but because you're not root, you login can't read the shadow password file. This isn't an issue with Gate One, just expected behavior in Linux.

      Switching url_prefix to /g1/ in server.conf was my first step before I fully got into the websocket side of things. I do agree that it **should** work, I am just unsure what steps to take from here. I haven't found anyone else yet that has gotten Apache+GateOne working. I have seen confirmation (but no "howto") that it works with nginx.

      Currently, GateOne is working standalone and I hope to get back in the near future to try proxying it behind Apache or nginx.

      Delete
  2. Did you ever get this working? Trying to get this set up on my home machine w/ apache also.

    ReplyDelete
    Replies
    1. I have it working perfectly fine as a standalone, but I have not yet gotten it working with Apache.

      The main issue is that support for websockets is still pretty spotty on the server side. nginx has it, and there is a module for lighttpd .

      GateOne itself uses Tornado , which is a pretty robust and secure server in its own right. In fact, when you look at it, websockects weren't standardized until late 2011. Tornado is probably one of the oldest and longest implementations of websockets, so if you compare that to the apache-websocket, Tornado has a lot more exposure.

      The only real reason to integrate with Apache is to put everything on one port (such as 443).

      I think the next step (to get it working with Apache) is to open an issue with the apache-websocket developer to see if he has any suggestions. If you do this, let me know.

      Delete
  3. Will just run it on another port for now. Using your init scripts w/ supervisord. Thanks.

    ReplyDelete
  4. Thanks for the info on PAM authentication for non-root users; I was having the same trouble. I also tried investigating the "api" authentication method, but couldn't find any documentation on this, so I have no idea how it's meant to work.

    I had exactly the same trouble with the Apache websocket proxy at http://blog.alex.org.uk/2012/02/16/using-apache-websocket-to-proxy-tcp-connection/. Somewhat confusingly the latest version on github is now called mod_websocket_vnc_proxy, not mod_websocket_tcp_proxy, so this needs to be changed in the WebSocketHandler line (the instructions in the author's blog are out of date). The result is the same as you described. My Apache config lines are:

    ProxyPass /g1/ws !
    ProxyPass /g1 http://127.0.0.1:8443/g1
    ProxyPassReverse /g1 http://127.0.0.1:8443/g1



    SetHandler websocket-handler
    WebSocketHandler /usr/lib/apache2/modules/mod_websocket_vnc_proxy.so vnc_proxy_init
    WebSocketTcpProxyBase64 on
    WebSocketTcpProxyHost 127.0.0.1
    WebSocketTcpProxyPort 8443



    We need ProxyPass for all the standard HTTP traffic, as well as the mod_websocket stuff. The websocket request from the browser uses the URL https://[domain]/g1/ws, so it's important that this request is handled by the WebSocketHandler, not by the ProxyPass. That's the reason for my line "ProxyPass /g1/ws !" (I don't know if it's actually necessary). However, I had the same trouble as everyone else: the websocket traffic from the browser does not get forwarded to GateOne. There is no "WebSocket opened" line in the GateOne logfile.

    I tried using tcpdump to investigate further. For this purpose I disabled SSL altogether, so both the browser->Apache and the Apache->GateOne connections are human-readable. The first thing I noticed is that my browser (Firefox) is not sending a Sec-WebSocket-Protocol line, so I don't know what protocol we are actually using (NB the same browser interacts perfectly well with GateOne on a direct port). Secondly, I noticed that Apache is acknowledging the "Upgrade: websocket" request from the browser, but it's not sending any corresponding request to GateOne. The WebSocketTcpProxyProtocol line does make a difference: if we set "WebSocketTcpProxyProtocol base64", for example, Apache returns a 403 Forbidden error to the browser when it tries to open the websocket, but if we remove the WebSocketTcpProxyProtocol line altogether, the browser get the correct 101 Switching Protocols response. In either case, however, there seems to be no corresponding request being sent to GateOne. It seems that Apache is opening a websocket with the browser, but doing nothing with it. I assume this is a bug in the Apache module.

    Let me know if anyone else finds a solution.

    ReplyDelete
  5. hello,

    i have installed gateOne today and i did not see how to connect directly to a SSH server.

    example:

    https://172.17.4.1/?ssh=72.251.34.4

    and it opens automatically the connection to 72.251.34.4.

    have any type to make this?

    thank you.

    ReplyDelete
  6. With the following virtualhost I can redirect a public address like https://gateone.bbbb.cc:443 to a private https://myserver.bbbb.cc:1234 (I have dnsmasq running on the server to provide the mapping of the domain bbb.cc into local addresses)


    SSLEngine on

    ServerName www.gateone.bbbb.cc
    ServerAlias gateone.bbbb.cc

    SSLCertificateFile /etc/apache2/ssl/apache.crt
    SSLCertificateKeyFile /etc/apache2/ssl/apache.key


    AuthType Basic
    AuthName "Restricted Files"
    AuthUserFile /etc/apache2/my.passwords
    Require valid-user

    RewriteEngine On

    RewriteCond %{HTTP_HOST} gateone.bbbb.cc$
    RewriteRule ^(.*)$ https://myserver.bbbb.cc:1234$1 [L,NC,R=301]

    ReplyDelete